package model;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.Observable;

import javax.swing.event.ChangeListener;
import javax.swing.event.UndoableEditListener;

import model.mediator.HubMediator;
import model.mediator.PieceMovementHub;
import model.memento.MementoSupport;
import model.pieces.Man;
import model.pieces.Piece;
import model.pieces.Target;

public class Level extends Observable implements PropertyChangeListener {

	/**
	 * Lista de piezas que forman el nivel
	 */
	private ArrayList<Piece> pieces;
	/**
	 * Nombre del nivel
	 */
	private String name;
	/**
	 * MementoSupport de los movimientos
	 */
	private MementoSupport undoSupport;
	/**
	 * Lista de listeners del evento endLevel
	 */
	private StateSupport endLevelEventListeners;

	public Level() {
		this.pieces = new ArrayList<Piece>();
		this.endLevelEventListeners = new StateSupport();
	}

	/**
	 * @param path
	 */
	public Level(URL path) {
		this.endLevelEventListeners = new StateSupport();

		loadLevel(path);
		setPiecesHub();
		setPiecesMemento();
		addBoxOnTargetEventListener();
	}

	/**
	 * @return String name
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * @return ArrayList<Piece> pieces
	 */
	public ArrayList<Piece> getPieces() {
		return this.pieces;
	}

	/**
	 * Cargar las piezas del nivel y el nombre.
	 * 
	 * @param path
	 *            file of the level
	 */
	public void loadLevel(URL path) {
		LevelReader reader = new LevelReader(path);
		this.name = reader.getName();
		this.pieces = reader.getPieces();

		notifyModelObservers();
	}

	/**
	 * @return Man pieza que representa el hombre.
	 */
	public Man getMan() {
		for (Piece piece : this.pieces) {
			if (piece instanceof Man) {
				return (Man) piece;
			}
		}
		return null;
	}

	/**
	 * Agregar a cada pieza el objeto que hace la funcion de hub de comunicacion
	 * de mensajes entre todas.
	 */
	private void setPiecesHub() {
		HubMediator mediator = new PieceMovementHub();
		for (Piece piece : this.pieces) {
			piece.setMediator(mediator);
		}
	}

	/**
	 * Agregar a las piezas que realizan movimiento el objeto que almacena todos
	 * lo movimientos para que puedan ser rehechos y deshechos.
	 */
	private void setPiecesMemento() {
		undoSupport = new MementoSupport();
		for (Piece piece : this.pieces) {
			if (piece.canMove()) {
				piece.setMementoSupport(undoSupport);
			}
		}
	}

	/**
	 * Agregar a las piezas Target el listener del evento BoxOnTarget para
	 * detectar si es fin de nivel.
	 */
	private void addBoxOnTargetEventListener() {
		for (Piece piece : this.pieces) {
			if (piece instanceof Target) {
				((Target) piece).addTargetStatePropertyListener(this);
			}
		}
	}

	/**
	 * Tratar evento de un Box llega a un Target.
	 * 
	 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
	 */
	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		for (Piece piece : this.pieces) {
			if ((piece instanceof Target)
					&& (((Target) piece).isBoxOnTarget() == false)) {
				return;
			}
		}
		// Comunicar evento a los controladores
		this.endLevelEventListeners.fireStateChangedEvent();
	}

	/**
	 * Agregar un listener a la lista de evento EndLevel.
	 * 
	 * @param listener
	 */
	public void addEndLevelEventListener(ChangeListener listener) {
		this.endLevelEventListeners.addStateChangeListener(listener);
	}

	/**
	 * Borrar un listener de la lista de evento EndLevel.
	 * 
	 * @param listener
	 */
	public void removeEndLevelEventListener(ChangeListener listener) {
		this.endLevelEventListeners.removeStateChangeListener(listener);
	}

	/**
	 * Agregar un listener a la lista de evento UndoSupport.
	 * 
	 * @param listener
	 */
	public void addMementoSupportEventListener(UndoableEditListener listener) {
		this.undoSupport.addUndoableEditListener(listener);
	}

	/**
	 * Borrar un listener de la lista de evento UndoSupport.
	 * 
	 * @param listener
	 */
	public void removeMementoSupportEventListener(UndoableEditListener listener) {
		this.undoSupport.removeUndoableEditListener(listener);
	}
	
	/**
	 * Notificar a los observadores del nivel si ha sucedido un cambio.
	 */
	private void notifyModelObservers() {
		this.setChanged();
		this.notifyObservers(this);
	}
}
